package protocol

import (
	"encoding/binary"
	"fmt"
	modbus "github.com/thinkgos/gomodbus/v2"
	"goAdapter/device/commInterface"
	"goAdapter/setting"
	"math"
	"strconv"
)

//var ProModbusTcp ProModbusTcpTemplate

//func readHoldReg(slaveAddr byte, regAddr uint16, regCnt uint16) []uint16 {
//	value, err := client.ReadHoldingRegisters(slaveAddr, regAddr, regCnt)
//	if err != nil {
//		setting.ZAPS.Errorf("read hold err")
//	}
//
//	return value
//}

type ProModbusTcpTemplate struct {
	Name string //协议名
	Sid  int    //从机地址
	//UseComIf  bool                                 //是否使用标准通信接口
	comIf     commInterface.CommunicationInterface //通信接口
	isConnect bool
	client    modbus.Client
}

func (c *ProModbusTcpTemplate) Construct() {
	c.Name = "modbusTCP"
	c.Sid = -1
	c.comIf = nil
	//c.UseComIf = false
	c.isConnect = false
}

func (c *ProModbusTcpTemplate) Init(inf commInterface.CommunicationInterface) bool {
	c.comIf = inf

	if inf.GetType() != commInterface.CommTypeTcpClient {
		setting.ZAPS.Errorf("err comm type")
	}

	para := inf.GetParam().TcpCParam
	setting.ZAPS.Debugf("protocol [%s] ip=[%s]", c.Name, para.IP)

	addr := fmt.Sprintf("%s:%s", para.IP, para.Port)
	p := modbus.NewTCPClientProvider(addr)

	c.client = modbus.NewClient(p)
	c.client.LogMode(false)

	return true
}

func (c *ProModbusTcpTemplate) Connect() bool {
	err := c.client.Connect()
	if err != nil {
		setting.ZAPS.Errorf("client connect err")
		c.isConnect = false
		return false
	}

	c.isConnect = true

	return true
}

func (c *ProModbusTcpTemplate) IsConnect() bool {
	return c.isConnect
}

func (c *ProModbusTcpTemplate) GetName() string {
	return c.Name
}

//func (c *ProModbusTcpTemplate) GetUseComIf() bool {
//	return c.UseComIf
//}

/*func (c *ProModbusTcpTemplate) GetNode(node *NodeProperty) int {
	setting.ZAPS.Debugf("%s get node %s", c.Name, node.Name)

	//先检查连接情况，如果没有连接，先尝试连接
	if !c.isConnect {
		ret := c.Connect()
		//如果还连接不成功，不再查询
		if !ret {
			return -1
		}
	}
	//byte := readHoldReg(byte(c.Sid), uint16(node.RegAddr), 1)
	//fmt.Println("read bytes=", byte)

	value, err := c.client.ReadHoldingRegisters(byte(c.Sid), uint16(node.RegAddr), 1)
	if err != nil {
		setting.ZAPS.Errorf("read hold err")
		return -1
	}

	node.Value = value[0]

	return 0
}*/

const (
	RegTypeCoilStatus  int = iota //线圈状态
	RegTypeInputStatus            //离散输入状态
	RegTypeHoldingReg             //保持寄存器
	RegTypeInputReg               //输入寄存器
)

const (
	DataTypeINT16 int = iota //整型
	DataTypeUINT16
	//DataTypeUBCD16
	DataTypeINT32
	DataTypeUINT32
	//DataTypeUBCD32
	DataTypeINT64
	DataTypeUINT64
	//DataTypeUBCD64
	DataTypeFLOAT //浮点
	//DataTypeDOUBLE
	DataTypeBIT //位
)

var regTypeMap = map[string]int{"线圈状态": RegTypeCoilStatus, "离散输入状态": RegTypeInputStatus, "保持寄存器": RegTypeHoldingReg, "输入寄存器": RegTypeInputReg}
var dataTypeMap = map[string]int{"INT16": DataTypeINT16, "BIT": DataTypeBIT, "FLOAT": DataTypeFLOAT, "UINT64": DataTypeUINT64, "INT64": DataTypeINT64, "UINT32": DataTypeUINT32, "INT32": DataTypeINT32, "UINT16": DataTypeUINT16}

func (c *ProModbusTcpTemplate) GetNode(node *NodeProperty) int {
	//setting.ZAPS.Debugf("%s get node %s", c.Name, node.ID)
	//先检查连接情况，如果没有连接，先尝试连接
	if !c.isConnect {
		ret := c.Connect()
		//如果还连接不成功，不再查询
		if !ret {
			return -1
		}
	}
	nodeRegType := regTypeMap[node.RegType]
	nodeDataType := dataTypeMap[node.DataType]

	if nodeRegType == RegTypeCoilStatus {
		switch nodeDataType {
		case DataTypeBIT:
			value, err := c.client.ReadCoils(byte(c.Sid), uint16(node.RegAddr), 1)
			if err != nil {
				setting.ZAPS.Errorf("read Coil err")
				return -1
			}
			node.Value = value[0]
			setting.ZAPS.Debugf("read data = %d", node.Value)
		default:
			{
				setting.ZAPS.Errorf("data type err")
				return -1
			}
		}
	} else if nodeRegType == RegTypeInputStatus {
		switch nodeDataType {
		case DataTypeBIT:
			value, err := c.client.ReadDiscreteInputs(byte(c.Sid), uint16(node.RegAddr), 1)
			if err != nil {
				setting.ZAPS.Errorf("read Discrete err")
				return -1
			}
			node.Value = value[0]
			setting.ZAPS.Debugf("read data = %d", node.Value)
		default:
			{
				setting.ZAPS.Errorf("data type err")
				return -1
			}
		}
	} else if nodeRegType == RegTypeHoldingReg {
		switch nodeDataType {
		case DataTypeINT16:
			fallthrough
		case DataTypeUINT16:
			value, err := c.client.ReadHoldingRegisters(byte(c.Sid), uint16(node.RegAddr), 1)
			if err != nil {
				setting.ZAPS.Errorf("read hold err")
				return -1
			}
			node.Value = value[0]
			//setting.ZAPS.Debugf("read data = %d", node.Value)
		case DataTypeINT32:
			fallthrough
		case DataTypeUINT32:
			value, err := c.client.ReadHoldingRegistersBytes(byte(c.Sid), uint16(node.RegAddr), 2)
			if err != nil {
				setting.ZAPS.Errorf("read hold err")
				return -1
			}
			node.Value = binary.BigEndian.Uint32(value)
			//node.Value = *(*uint32)(unsafe.Pointer(&value))
			setting.ZAPS.Debugf("read data = %d", node.Value)
		case DataTypeFLOAT:
			value, err := c.client.ReadHoldingRegistersBytes(byte(c.Sid), uint16(node.RegAddr), 2)
			if err != nil {
				setting.ZAPS.Errorf("read hold err")
				return -1
			}
			bit := binary.BigEndian.Uint32(value)
			node.Value = math.Float32frombits(bit)
			//node.Value = *(*float32)(unsafe.Pointer(&value))
			setting.ZAPS.Debugf("read data = %f", node.Value)
		case DataTypeINT64:
			fallthrough
		case DataTypeUINT64:
			value, err := c.client.ReadHoldingRegistersBytes(byte(c.Sid), uint16(node.RegAddr), 4)
			if err != nil {
				setting.ZAPS.Errorf("read hold err")
				return -1
			}
			node.Value = binary.BigEndian.Uint64(value)
			//node.Value = *(*uint64)(unsafe.Pointer(&value))
			setting.ZAPS.Debugf("read data = %d", node.Value)
		default:
			{
				setting.ZAPS.Errorf("data type err")
				return -1
			}
		}
	} else if nodeRegType == RegTypeInputReg {
		switch nodeDataType {
		case DataTypeINT16:
			fallthrough
		case DataTypeUINT16:
			value, err := c.client.ReadInputRegisters(byte(c.Sid), uint16(node.RegAddr), 1)
			if err != nil {
				setting.ZAPS.Errorf("read Input err")
				return -1
			}
			node.Value = value
			setting.ZAPS.Debugf("read data = %d", node.Value)
		case DataTypeINT32:
			fallthrough
		case DataTypeUINT32:
			value, err := c.client.ReadInputRegistersBytes(byte(c.Sid), uint16(node.RegAddr), 2)
			if err != nil {
				setting.ZAPS.Errorf("read Input err")
				return -1
			}
			node.Value = binary.BigEndian.Uint32(value)
			//node.Value = *(*uint32)(unsafe.Pointer(&value))
			setting.ZAPS.Debugf("read data = %d", node.Value)
		case DataTypeFLOAT:
			value, err := c.client.ReadInputRegistersBytes(byte(c.Sid), uint16(node.RegAddr), 2)
			if err != nil {
				setting.ZAPS.Errorf("read Input err")
				return -1
			}
			bit := binary.BigEndian.Uint32(value)
			node.Value = math.Float32frombits(bit)
			//node.Value = *(*float32)(unsafe.Pointer(&value))
			setting.ZAPS.Debugf("read data = %f", node.Value)
		case DataTypeINT64:
			fallthrough
		case DataTypeUINT64:
			value, err := c.client.ReadInputRegistersBytes(byte(c.Sid), uint16(node.RegAddr), 4)
			if err != nil {
				setting.ZAPS.Errorf("read Input err")
				return -1
			}
			node.Value = binary.BigEndian.Uint64(value)
			//node.Value = *(*uint64)(unsafe.Pointer(&value))
			setting.ZAPS.Debugf("read data = %d", node.Value)
		default:
			{
				setting.ZAPS.Errorf("data type err")
				return -1
			}
		}
	} else {
		setting.ZAPS.Errorf("%s get node %s type err", c.Name, node.ID)
		return -1
	}

	return 0
}
func (c *ProModbusTcpTemplate) SetNode(node *NodeProperty) int {
	setting.ZAPS.Debugf("%s set node %s", c.Name, node.ID)
	var array []byte

	nodeRegType := regTypeMap[node.RegType]
	nodeDataType := dataTypeMap[node.DataType]

	if nodeRegType == RegTypeCoilStatus {
		switch nodeDataType {
		case DataTypeBIT:
			val, err := strconv.ParseBool(fmt.Sprint(node.SetValue.(string)))
			if err != nil {
				setting.ZAPS.Errorf("write coil err, node value change bool err")
				return -1
			}
			err = c.client.WriteSingleCoil(byte(c.Sid), uint16(node.RegAddr), val)
			if err != nil {
				setting.ZAPS.Errorf("write coil err")
				return -1
			}
		default:
			{
				setting.ZAPS.Errorf("data type err")
				return -1
			}
		}
	} else if nodeRegType == RegTypeHoldingReg {
		switch nodeDataType {
		case DataTypeFLOAT:
			val, err := strconv.ParseFloat(node.SetValue.(string), 16)
			if err != nil {
				setting.ZAPS.Errorf("data is invalid")
				return -1
			}
			bit := math.Float32bits(float32(val))
			binary.BigEndian.PutUint32(array, bit)
			err = c.client.WriteMultipleRegistersBytes(byte(c.Sid), uint16(node.RegAddr), 2, array)
			if err != nil {
				setting.ZAPS.Errorf("write Reg err")
				return -1
			}
		case DataTypeINT16:
			fallthrough
		case DataTypeUINT16:
			val, err := strconv.ParseUint(node.SetValue.(string), 10, 16)
			if err != nil {
				setting.ZAPS.Errorf("data is invalid")
				return -1
			}
			err = c.client.WriteSingleRegister(byte(c.Sid), uint16(node.RegAddr), uint16(val))
			if err != nil {
				setting.ZAPS.Errorf("write Reg err")
				return -1
			}
		case DataTypeINT32:
			fallthrough
		case DataTypeUINT32:
			val, err := strconv.ParseUint(node.SetValue.(string), 10, 16)
			if err != nil {
				setting.ZAPS.Errorf("data is invalid")
				return -1
			}
			binary.BigEndian.PutUint32(array, uint32(val))
			err = c.client.WriteMultipleRegistersBytes(byte(c.Sid), uint16(node.RegAddr), 2, array)
			if err != nil {
				setting.ZAPS.Errorf("write Reg err")
				return -1
			}
		case DataTypeINT64:
			fallthrough
		case DataTypeUINT64:
			val, err := strconv.ParseUint(node.SetValue.(string), 10, 16)
			if err != nil {
				setting.ZAPS.Errorf("data is invalid")
				return -1
			}
			binary.BigEndian.PutUint64(array, val)
			err = c.client.WriteMultipleRegistersBytes(byte(c.Sid), uint16(node.RegAddr), 4, array)
			if err != nil {
				setting.ZAPS.Errorf("write Reg err")
				return -1
			}
		default:
			{
				setting.ZAPS.Errorf("data type err")
				return -1
			}
		}
	} else {
		setting.ZAPS.Debugf("%s set node %s type err", c.Name, node.ID)
		return -1
	}
	return 0
}

func (c *ProModbusTcpTemplate) GetSid() int {
	return c.Sid
}

func (c *ProModbusTcpTemplate) SetSid(id int) {
	c.Sid = id
}

func (c *ProModbusTcpTemplate) GetComInf() commInterface.CommunicationInterface {
	return c.comIf
}
